#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UTable.Input;
using AdvanceMath;
using Physics2DDotNet;
using Physics2DDotNet.Detectors;
using Physics2DDotNet.Ignorers;
using Physics2DDotNet.Joints;
using Physics2DDotNet.Shapes;
using Physics2DDotNet.Solvers;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using UTable.ObjectManager.Commands;
using UTable.Input.MultiTouch;
using System.Windows.Media;

namespace UTable.Objects.Policies
{
    /// <summary>
    /// A layout policy that provides physical interaction experience, 
    /// such as inertia, collision, etc. 
    /// </summary>
    public class PhysicalLayoutPolicy: LayoutPolicy
    {

        class ScaleState
        {
            public double Scale { get; set; }
            public Point Center { get; set; }

            public ScaleState()
            {
                Scale = 1;
            }
        }

        class JoinInfo
        {
            public IObject Obj1;
            public IObject Obj2;
            public List<Joint> Joints = new List<Joint>();
        }

        class ObjectInfo
        {
            public double Height;
            public double Width;
            public double Orientation;
            public double ScaleX;
            public double ScaleY;
            public Point Position;
            public Point RotateCenter;
            public Point ScaleCenter;
            public IObject Obj;
        }

        PhysicsEngine engine;
        PhysicsTimer timer;
        ObjectIgnorer ignorer;
        Dictionary<IObject, Body> elementToBody;
        Dictionary<IObject, ScaleState> elementToScale;
        List<IObject> shouldCreateBody;
        List<IObject> shouldRemoveBody;
        Dictionary<int, FixedHingeJoint> contactJoints;
        Dictionary<IObject, Dictionary<int, PhysicalContact>> elementToContacts;
        IObject currentUpdateObject;
        PhysicalCollisionPolicy collisionPolicy;
        PhysicalDampingPolicy dampingPolicy;
        PhysicalScalePolicy scalePolicy;
        List<IObject> movingObjects;
        double minBodyWidth = 100;
        double minBodyHeight = 100;
        List<JoinInfo> JoinInfoList = new List<JoinInfo>();
        private ObjectInfo _maximizedObjectInfo = null;
        float _updateInterval = 0.03f;
        float _wallRestitution = 0.3f;
        List<Body> _wallComponents;

        private double getScaleChangeRatio(double cd, double pd)
        {
            if (pd == 0)
            {
                Console.WriteLine("Physical Layout Error: previous distance is zero!");
                return 1;
            }
            double alpha = 1;
            double halfAttenuationDistance = 50;
            double beta = 0.7 * halfAttenuationDistance;
            return cd / pd * alpha * Math.Exp(-beta / pd);
        }

        /// <summary>
        /// PhysicalLayoutPolicy Constructor
        /// </summary>
        /// <param name="target">The target object to which this policy is applied</param>
        /// <param name="param">The param for creating this policy, should be the type PhysicalLayoutPolicyParameter </param>
        public PhysicalLayoutPolicy(IObject target, object param): base(target, param)
		{
            elementToBody = new Dictionary<IObject, Body>();
            shouldCreateBody = new List<IObject>();
            shouldRemoveBody = new List<IObject>();
            contactJoints = new Dictionary<int, FixedHingeJoint>();
            elementToScale = new Dictionary<IObject, ScaleState>();
            ignorer = new ObjectIgnorer();
            elementToContacts = new Dictionary<IObject, Dictionary<int, PhysicalContact>>();
            movingObjects = new List<IObject>();

            engine = new PhysicsEngine();
            engine.BroadPhase = new SweepAndPruneDetector();
            engine.Solver = new SequentialImpulsesSolver();
            timer = new PhysicsTimer(PhysicsTimerCallback, UpdateInterval);
            UpdateParameter(param);

            Target.ObjectLoaded += new EventHandler(PhysicalLayoutPolicy_Loaded);
            ObjectOutDetector = new ObjectOutDetector(target);
            ObjectOutDetector.ObjectOutDetected += new ObjectOutDetectedEventHandler(OnObjectOutDetected);
            Run();
        }

        /// <summary>
        /// Update the parameters of this policy
        /// </summary>
        /// <param name="param">The parameter, which should be of the type PhysicalLayoutPolicyParameter</param>
        public override void UpdateParameter(object param)
        {
            base.UpdateParameter(param);
            PhysicalLayoutPolicyParameter parameter = param as PhysicalLayoutPolicyParameter;
            if (parameter == null)
                return;
            this.DefaultLinearDamping = parameter.DefaultLinearDamping;
            this.DefaultAngularDamping = parameter.DefaultAngularDamping;
            this.EnableWalls = parameter.EnableWalls;
            this.ScalePolicy = parameter.ScalePolicy;
            this.CollisionPolicy = parameter.CollisionPolicy;
            this.DampingPolicy = parameter.DampingPolicy;
            timer.TargetInterval = (float)parameter.UpdateInterval;
            EnableWalls = parameter.EnableWalls;
        }

        void OnObjectOutDetected(object sender, ObjectOutDetectedEventArgs args)
        {
            // Console.WriteLine("Move the object " + args.Target.ObjectType + ": " + this.Target.ObjectType + "->" + args.DestContainer.ObjectType);
            if (_maximizedObjectInfo == null && args.DestContainer != null && args.DestContainer.CanHoldObjects)
                MoveObject(args.Target, args.DestContainer);
        }

        void PhysicalLayoutPolicy_Loaded(object sender, EventArgs e)
        {
            Run();
        }

        void Run()
        {
            if (Target == null)
                return;

            if (!DesignerProperties.GetIsInDesignMode(Application.Current.MainWindow))
                timer.IsRunning = true;

            if (EnableWalls)
                CreateWalls(Target);

            foreach (IObject target in Target.OwnedObjects)
            {
                CreateBody(target);
            }
        }

        #region Private Methods

        private void CreateWalls(IObject target)
        {
            float wallSize = 4000.0f;       // the size of the wall. (Give a large number so that objects cannot pass through the wall)
            float wallrestitution = WallRestitution;   // the restitution of the wall (反弹力）

            var wallCoff = new Coefficients(wallrestitution, 1.0f);
            var wallLife = new Lifespan();

            var flrState = new PhysicsState(new ALVector2D((float)0.0, ((float)target.Width) * ((float)0.5), (float)(target.Height + wallSize / 2)));
            var flrShape = new PolygonShape(VertexHelper.CreateRectangle((float)target.Width, wallSize), 2);
            var bdyFloor = new Body(flrState, flrShape, float.PositiveInfinity, wallCoff, wallLife);

            var ceiState = new PhysicsState(new ALVector2D((float)0.0, (float)(target.Width * 0.5), (float)-wallSize / 2));
            var ceiShape = new PolygonShape(VertexHelper.CreateRectangle((float)target.Width, wallSize), 2);
            var bdyCeiling = new Body(ceiState, ceiShape, float.PositiveInfinity, wallCoff, wallLife);

            var lwlState = new PhysicsState(new ALVector2D((float)0.0, (float)-wallSize / 2, ((float)target.Height) * ((float)0.5)));
            var lwlShape = new PolygonShape(VertexHelper.CreateRectangle(wallSize, (float)target.Height), 2);
            var bdyLeftWall = new Body(lwlState, lwlShape, float.PositiveInfinity, wallCoff, wallLife);

            var rwlState = new PhysicsState(new ALVector2D((float)0.0, (float)target.Width + (float)wallSize / 2, ((float)target.Height) * ((float)0.5)));
            var rwlShape = new PolygonShape(VertexHelper.CreateRectangle(wallSize, (float)target.Height), 2);
            var bdyRightWall = new Body(rwlState, rwlShape, float.PositiveInfinity, wallCoff, wallLife);

            engine.AddBody(bdyFloor);
            engine.AddBody(bdyCeiling);
            engine.AddBody(bdyLeftWall);
            engine.AddBody(bdyRightWall);

            // save the wall components in the list
            // so that operations such as changing restitution can be easy
            _wallComponents = new List<Body>();
            _wallComponents.Add(bdyFloor);
            _wallComponents.Add(bdyCeiling);
            _wallComponents.Add(bdyLeftWall);
            _wallComponents.Add(bdyRightWall);
        }

        private void SetZTop(IObject target)
        {
            if (Target == null)
                return;

            int zIndex = 0;
            foreach (IObject child in Target.OwnedObjects)
                zIndex = Math.Max(zIndex, child.ZIndex);
            if (zIndex == int.MaxValue)
            {
                foreach (IObject child in Target.OwnedObjects)
                    child.ZIndex = 0;
                zIndex = 0;
            }
            zIndex++;
            target.ZIndex = zIndex;

            // if another object is joined to this object, then the object should be put top
            foreach (JoinInfo info in JoinInfoList)
            {
                if (info.Obj1 == target)
                {
                    SetZTop(info.Obj2);
                }
            }
        }

        private void GetMultiTouchInput(IObject obj, MultiTouchEventArgs args)
        {
            foreach (FingerEventArgs e in args.FingerEvents)
            {
                switch (e.EventType)
                {
                    case FingerEventType.FINGER_DOWN_EVENT:
                        OnNewContact(obj, e);
                        break;
                    case FingerEventType.FINGER_IN_EVENT:
                        OnNewContact(obj, e);
                        break;
                    case FingerEventType.FINGER_MOVE_EVENT:
                        OnContactMoved(obj, e);
                        break;
                    case FingerEventType.FINGER_UP_EVENT:
                        OnContactRemoved(obj, e);
                        break;
                    case FingerEventType.FINGER_OUT_EVENT:
                        OnContactRemoved(obj, e);
                        break;
                }
            }
        }

        private void OnNewContact(IObject target, FingerEventArgs e)
        {
            // The object should not receive input when it is maximized
            if (_maximizedObjectInfo != null && _maximizedObjectInfo.Obj == target)
            {
                return;
            }

            // if (target.IsLayoutLocked)
            //    return;
            if (elementToContacts[target].ContainsKey(e.FingerID))
                return;

            Point position = target.TranslatePoint(e.Position, Target);
            Body body;
            if (elementToBody.TryGetValue(target, out body))
            {
                Vector2D contactPoint = new Vector2D((float)position.X, (float)position.Y);
                if (body.Shape.CanGetIntersection)
                {
                    Vector2D temp = body.Matrices.ToBody * contactPoint;
                    IntersectionInfo intersectionInfo;
                    // if (body.Shape.TryGetIntersection(temp, out intersectionInfo))
                    {
                        FixedHingeJoint joint = new FixedHingeJoint(body, contactPoint, new Lifespan());
                        joint.Softness = (float)0.3;    // TODO
                        engine.AddJoint(joint);
                        contactJoints[e.FingerID] = joint;
                    }
                }
                elementToContacts[target].Add(e.FingerID, new PhysicalContact(e.FingerID, position));
            }
            SetZTop(target);
            target.CaptureFinger(e.FingerID);
            ObjectOutDetector.FreezeObject(target);
        }

        private void OnContactMoved(IObject target, FingerEventArgs e)
        {
            // if (target.IsLayoutLocked)
            //    return;

            // The object should not receive input when it is maximized
            if (_maximizedObjectInfo != null && _maximizedObjectInfo.Obj == target)
            {
                return;
            }

            // Console.WriteLine("Finger Move: " + e.Position);
            if (!elementToContacts[target].ContainsKey(e.FingerID))
                return;
            Point position = target.TranslatePoint(e.Position, Target);

            // if the movement is detected as noise, just do nothing
            if (!elementToContacts[target][e.FingerID].UpdatePosition(position))
                return;

            FixedHingeJoint joint;
            if (contactJoints.TryGetValue(e.FingerID, out joint))
            {
                // move
                joint.Anchor = new Vector2D((float)position.X, (float)position.Y);

                //scale
                Body body = joint.Bodies.First();
                IObject element = body.Tag as IObject;
                if (element != null && elementToContacts[target].Count > 1 && (element is DependencyObject) && GetIsScalable(element as DependencyObject))
                {
                    ScaleState state;
                    if (elementToScale.TryGetValue(element, out state))
                    {
                        // Console.WriteLine(elementToContacts[target][0].FingerID + ", " + elementToContacts[target][1].FingerID);
                        double previousDistance = 0;
                        double currentDistance = 0;
                        double initialDistance = 0;
                        int divisor = 0;
                        PhysicalContact[] contactsArray = elementToContacts[element].Values.ToArray();

                        Point center = new Point(element.Width / 2, element.Height / 2);

                        for (int i = 0; i < contactsArray.Length; i++)
                        {
                            for (int j = i + 1; j < contactsArray.Length; j++)
                            {
                                Vector vector = Target.TranslatePoint(contactsArray[j].Position, target) -
                                    Target.TranslatePoint(contactsArray[i].Position, target);
                                currentDistance += vector.Length;

                                Vector previousVector = Target.TranslatePoint(contactsArray[j].PreviousPosition, target) -
                                    Target.TranslatePoint(contactsArray[i].PreviousPosition, target);
                                previousDistance += previousVector.Length;

                                Vector initialVector = Target.TranslatePoint(contactsArray[j].InitialPosition, target) -
                                    Target.TranslatePoint(contactsArray[i].InitialPosition, target);
                                initialDistance += initialVector.Length;

                                divisor++;
                            }
                        }
                        if (divisor == 0)
                            divisor = 1;

                        previousDistance /= divisor;
                        currentDistance /= divisor;
                        initialDistance /= divisor;

						double delta = 1;

						if (ScalePolicy != null)
						{
							// use the scale policy
							delta = this.ScalePolicy.GetScaleChangeRatio(Target, target, state.Scale, currentDistance, previousDistance, initialDistance);
						}
						else
						{
							// use the default policy

							// determine the change 
							double M = 500;
							double t = 1 / (Math.Log(1 / 0.99) * M);
							delta = currentDistance / previousDistance;
							delta = 1 + (currentDistance - previousDistance) / (previousDistance + 400) * 0.9;
							double maxScale = 0.04;
							if (delta > 1 + maxScale)
								delta = 1 + maxScale;
							else if (delta < 1 - maxScale)
								delta = 1 - maxScale;

							// Console.WriteLine(previousDistance + ": " + currentDistance);

							if (double.IsNaN(delta))
								delta = 1;

							// calculate the max scale
							double minscale = MinScale;
							double maxscale = MaxScale;

							double maxFillRaito = 0.8;
							double maxAllowedWidthScale = this.Target.Width / target.Width * maxFillRaito;
							double maxAllowedHeightScale = this.Target.Height / target.Height * maxFillRaito;
							double maxAllowedScale = Math.Min(maxAllowedHeightScale, maxAllowedWidthScale);
							if (maxscale > maxAllowedScale)
								maxscale = maxAllowedScale;

							var newScale = state.Scale * delta;
							if (newScale > maxscale)
								delta = maxscale / state.Scale;
							else if (newScale < MinScale)
								delta = MinScale / state.Scale;
						}

                        state.Scale *= delta;
                        state.Center = center;
                        body.Transformation *= Matrix2x3.FromScale(new Vector2D((float)delta, (float)delta));
                    }
                }
            }
        }

        private void OnContactRemoved(IObject target, FingerEventArgs e)
        {
            // The object should not receive input when it is maximized
            if (_maximizedObjectInfo != null && _maximizedObjectInfo.Obj == target)
            {
                return;
            }

            // if (target.IsLayoutLocked)
            //    return;
            if (!elementToContacts[target].ContainsKey(e.FingerID))
                return;
            // Console.WriteLine("Finger UP: " + e.Position);
            FixedHingeJoint removedJoint;
            if (contactJoints.TryGetValue(e.FingerID, out removedJoint))
            {
                removedJoint.Lifetime.IsExpired = true;
                contactJoints.Remove(e.FingerID);
            }
            if (elementToContacts[target].ContainsKey(e.FingerID))
            {
                elementToContacts[target].Remove(e.FingerID);
            }
            if (elementToContacts[target].Count == 0)
            {
                if (!IsObjectMoving(target))
                    ObjectOutDetector.UnfreezeObject(target);
                ObjectOutDetectedEventArgs r = ObjectOutDetector.DetectObjectOut(target);
                if (r != null)
                {
                    // Console.WriteLine("No Contacts Check");
                    OnObjectOutDetected(this, r);
                }
            }
        }

        #endregion

        #region Physical Layout Methods

        protected void OnObjectStartMove(IObject obj)
        {
            if (!movingObjects.Contains(obj))
            {
                movingObjects.Add(obj);
                ObjectOutDetector.FreezeObject(obj);
            }
			if (obj is IObjectEntity)
				(obj as IObjectEntity).RaiseEvent(ObjectEventType.DragStarted, new ObjectDragEventArgs());
        }
        
        protected void OnObjectStopMove(IObject obj)
        {
            movingObjects.Remove(obj);
            if (elementToContacts[obj].Count == 0)
            {
                ObjectOutDetector.UnfreezeObject(obj);
                ObjectOutDetector.CheckObject(obj);
            }
			if (obj is IObjectEntity)
				(obj as IObjectEntity).RaiseEvent(ObjectEventType.DragEnded, new ObjectDragEventArgs());
        }

        protected bool IsObjectMoving(IObject o)
        {
            return movingObjects.Contains(o);
        }

        void PhysicsTimerCallback(float dt, float trueDt)
        {
            engine.Update(dt, trueDt);
            if (Application.Current != null && Application.Current.Dispatcher != null)
                Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)UpdateChildren);
        }


        /// <summary> 
        /// Updates the child positions according to the physical engine
        /// </summary>
        protected virtual void UpdateChildren()
        {
            foreach (Body body in engine.Bodies)
            {
                Boolean bodyChanged = false;
                IObject child = body.Tag as IObject;
                if (child == null)
                    continue;

                // The object should not be updated when it is maximized
                if (_maximizedObjectInfo != null && _maximizedObjectInfo.Obj == child)
                {
                    continue;
                }

                Vector2D linearPosition = body.State.Position.Linear;
                currentUpdateObject = child;
                Point pos = new Point(linearPosition.X - (child.Width / 2), 
                    linearPosition.Y - (child.Height / 2));
                // modify the position precision
                pos = new Point(Math.Round(pos.X, 1), Math.Round(pos.Y, 1));
                if (child.Position != pos)
                {
                    child.Position = pos;
                    bodyChanged = true;
                }

                double angleInDegrees = MathHelper.ToDegrees(body.State.Position.Angular);
                angleInDegrees = Math.Round(angleInDegrees, 1);
                if (child.Orientation != angleInDegrees)
                {
                    child.Orientation = angleInDegrees;
                    bodyChanged = true;
                }
                Point rotateCenter = new Point(child.Width / 2, child.Height / 2);
                if (child.RotateCenter != rotateCenter)
                {
                    child.RotateCenter = rotateCenter;
                    bodyChanged = true;
                }

                ScaleState state;
                if (elementToScale.TryGetValue(child, out state))
                {
                    if (child.ScaleX != state.Scale)
                    {
                        child.ScaleX = state.Scale;
                        bodyChanged = true;
                    }
                    if (child.ScaleY != state.Scale)
                    {
                        child.ScaleY = state.Scale;
                        bodyChanged = true;
                    }
                    child.ScaleCenter = state.Center;
                }
                if (!bodyChanged)
                {
                    if (movingObjects.Contains(child))
                    {
                        OnObjectStopMove(child);
                    }
                }
                else
                {
                    if (!movingObjects.Contains(child))
                        OnObjectStartMove(child);
                }
                currentUpdateObject = null;
            }
        }

        /// <summary>
        /// Create a physical body for the object
        /// the body will be added to the physical engine
        /// </summary>
        /// <param name="target"></param>
        public void CreateBody(IObject target)
        {
            double angle = 0;
            double width = minBodyWidth;
            double height = minBodyHeight;
            if (target.Width > 0 && !double.IsNaN(target.Width))
                width = target.Width;
            if (target.Height > 0 && !double.IsNaN(target.Height))
                height = target.Height;

            //if an element already has rotate transform, get it's angle.
            angle = MathHelper.ToRadians((float)target.Orientation);
            PhysicsState state = new PhysicsState(new ALVector2D((float)angle, 
                (float)target.Position.X + ((float)width / 2), 
                (float)target.Position.Y + (float)(height / 2)));
            IShape shape = CreateShape(target, width, height);

            MassInfo mass = MassInfo.FromPolygon(shape.Vertexes, 1);
            Body body = new Body(state, shape, mass, new Coefficients(0.6f, (float)0.95d), new Lifespan());
            
            if (DampingPolicy != null)
            {
                body.LinearDamping = (float)DampingPolicy.GetLinearDampingValue(target);
                body.AngularDamping = (float)DampingPolicy.GetAngularDampingValue(target);
            }
            else
            {
                body.LinearDamping = (float)DefaultLinearDamping;
                body.AngularDamping = (float)DefaultAngularDamping;
            }
            body.Transformation *= Matrix2x3.FromScale(new Vector2D((float)target.ScaleX, (float)target.ScaleY));
            ScaleState ss = new ScaleState();
            ss.Scale = target.ScaleX;
            ss.Center = new Point(width / 2, height / 2);
            if (target.Clip is EllipseGeometry)
            {
                // body.Transformation *= Matrix2x3.FromScale(new Vector2D((float)1, (float)(height / width)));
            }
            if (EnableWalls)
            {
                body.IsCollidable = true;
                if (CollisionPolicy != null)
                    body.CollisionIgnorer = CollisionPolicy.GetCollisionIgnorer(target);
            }
            else
                body.IsCollidable = false;
            body.Tag = target;
            engine.AddBody(body);
            elementToBody.Add(target, body);
            elementToScale.Add(target, ss);
            elementToContacts.Add(target, new Dictionary<int, PhysicalContact>());
            SetZTop(target);
            target.LayoutChanged += OnTargetLayoutChanged;

            // Initialize the state of the object in the layout policy
            if (target.LayoutState == ObjectLayoutState.Maximized)
            {
                // maximize the object
                ChangeLayoutState(target, ObjectLayoutState.Maximized);
            }
        }

        /// <summary>
        /// Remove the physical body for the target object
        /// </summary>
        /// <param name="target"></param>
        public void RemoveBody(IObject target)
        {
            Body body;
            if (elementToBody.TryGetValue(target, out body))
            {
                body.Lifetime.IsExpired = true;
                body.Tag = null;
                elementToBody.Remove(target);
                elementToScale.Remove(target);
                elementToContacts.Remove(target);
                target.LayoutChanged -= OnTargetLayoutChanged;
                if (movingObjects.Contains(target))
                {
                    movingObjects.Remove(target);
                }
                if (currentUpdateObject == target)
                    currentUpdateObject = null;
            }
            // unjoin all the joints
            foreach (JoinInfo info in JoinInfoList)
            {
                if (info.Obj1 == target || info.Obj2 == target)
                {
                    ObjectUnjoinCommandArgs args = new ObjectUnjoinCommandArgs();
                    args.Obj1 = info.Obj1;
                    args.Obj2 = info.Obj2;
                    this.UnjoinObjects(args);
                }
            }
        }

        void OnTargetLayoutChanged(IObject target, ObjectLayoutChangedEventArgs args)
        {
            Body body;
            if (elementToBody.TryGetValue(target, out body))
            {
                switch (args.Type)
                {
                    case ObjectLayoutType.Size:
                        double width = minBodyWidth;
                        double height = minBodyHeight;
                        if (target.Width > 0 && !double.IsNaN(target.Width))
                            width = target.Width;
                        if (target.Height > 0 && !double.IsNaN(target.Height))
                            height = target.Height;
                        body.Shape = CreateShape(target, width, height);
                        MassInfo mass = MassInfo.FromPolygon(body.Shape.Vertexes, 1);
                        body.Mass.MomentOfInertia = mass.MomentOfInertia;
                        body.Mass.Mass = mass.Mass;
                        break;
                }
            }
        }

        private IShape CreateShape(IObject target, double width, double height)
        {
            IShape shape = null;

            if (target.Clip is PathGeometry)
            {
                /*
                PathGeometry polygon = target.Clip as PathGeometry;
                double centerX = polygon.GeometryCenterX;
                double centerY = polygon.GeometryCenterY;
                Vector2D[] vertices = new Vector2D[polygon.Points.Count + 1];
                vertices[0] = new Vector2D((float)(polygon.StartPoint.X - centerX), (float)(polygon.StartPoint.Y - centerY));
                for (int i = 0; i < polygon.Points.Count; i++)
                {
                    vertices[i + 1] = new Vector2D((float)(polygon.Points[i].X - centerX), (float)(polygon.Points[i].Y - centerY));
                }
                shape = new PolygonShape(vertices, 2);
                return shape;*/
            }

            if (target.Clip is EllipseGeometry)
            {
                EllipseGeometry ellipse = target.Clip as EllipseGeometry;
                shape = new CircleShape((float)ellipse.RadiusX / 2, 50);
                return shape;
            }

            shape = new PolygonShape(VertexHelper.CreateRectangle((float)width, (float)height), 2);
            // Console.WriteLine("Create Body: " + this.Container + ", " + target + ": " + target.BoundWidth + ", " + target.BoundHeight);
            return shape;
		}

		public void SetObjectLayout(IObject obj, Point position, double orientation, Size size, Size scale, Geometry geometry)
        {
            SetPosition(obj, position);
            SetOrientation(obj, orientation);
            SetSize(obj, size);
            SetScale(obj, scale);
            SetShape(obj, geometry);
        }

        /// <summary>
        /// Set the position for the object
        /// the position of body in the physical engine will be updated at the same time
        /// </summary>
        public void SetPosition(IObject obj, Point position)
        {
            Body body = elementToBody[obj];
            double width = obj.Width;
            double height = obj.Height;
            body.State.Position.Linear = new Vector2D((float)position.X + ((float)width / 2),
                (float)position.Y + (float)(height / 2));
        }
        /// <summary>
        /// Set the orientation for the object
        /// the orientation of body in the physical engine will be updated at the same time
        /// </summary>
        public void SetOrientation(IObject obj, double orientation)
        {
            Body body = elementToBody[obj];
            double width = obj.Width;
            double height = obj.Height;
            body.State.Position.Angular = MathHelper.ToRadians((float)orientation);
        }
        public void SetScale(IObject obj, Size scale)
        {

        }
        public void SetSize(IObject obj, Size size)
        {
            // Body body = elementToBody[obj];
        }


        public void SetShape(IObject obj, Geometry shape)
        {
        }

        #endregion

        #region Properties

        public PhysicalCollisionPolicy CollisionPolicy
        {
            get
            {
                return collisionPolicy;
            }
            set
            {
                if (value == collisionPolicy)
                    return;
                collisionPolicy = value;
                if (value == null)
                    return;
                foreach (IObject obj in elementToBody.Keys)
                {
                    elementToBody[obj].CollisionIgnorer = value.GetCollisionIgnorer(obj);
                }
            }
        }

        public PhysicalDampingPolicy DampingPolicy
        {
            get
            {
                return dampingPolicy;
            }
            set
            {
                if (value == dampingPolicy)
                    return;
                dampingPolicy = value;
                if (dampingPolicy == null)
                    return;
                foreach (IObject obj in elementToBody.Keys)
                {
                    elementToBody[obj].LinearDamping = (float)dampingPolicy.GetLinearDampingValue(obj);
                    elementToBody[obj].AngularDamping = (float)dampingPolicy.GetAngularDampingValue(obj);
                }
            }
        }

        public PhysicalScalePolicy ScalePolicy
        {
            get
            {
                return scalePolicy;
            }
            set
            {
                scalePolicy = value;
            }
        }

        public double DefaultLinearDamping
        {
            get;
            set;
        }

        public double DefaultAngularDamping
        {
            get;
            set;
        }

        /// <summary>
        /// The update interval of the physical engine, measured with seconds
        /// The default is 0.03f
        /// </summary>
        public float UpdateInterval
        {
            get
            {
                return _updateInterval;
            }
            set
            {
                _updateInterval = value;
                if (timer != null)
                    timer.TargetInterval = value;
            }
        }

        /// <summary>
        /// The restitution of the wall (Bound Force)
        /// The value should be in the range [0.0f, 1.0f]
        /// The default is 0.3f
        /// </summary>
        public float WallRestitution
        {
            get
            {
                return _wallRestitution;
            }
            set
            {
                _wallRestitution = value;
                if (_wallComponents != null)
                {
                    foreach (Body body in _wallComponents)
                        body.Coefficients = new Coefficients(value, 1.0f);
                }
            }
        }

        #region MinScale

        private double _minScale = 0.6;

        public double MinScale
        {
            get { return _minScale; }
            set { _minScale = value; }
        }

        #endregion

        #region MaxScale

        private double _maxScale = 1.6;

        public double MaxScale
        {
            get { return _maxScale; }
            set { _maxScale = value; }
        }

        #endregion

        #region Attached - IsScalable

        /// <summary>
        /// IsScalable Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty IsScalableProperty =
            DependencyProperty.RegisterAttached("IsScalable", typeof(bool), typeof(DependencyObject),
                new UIPropertyMetadata(true));

        /// <summary>
        /// Gets the IsScalable property.  This dependency property 
        /// indicates if the element is scalable.
        /// </summary>
        public static bool GetIsScalable(DependencyObject d)
        {
            return (bool)d.GetValue(IsScalableProperty);
        }

        /// <summary>
        /// Sets the IsScalable property.  This dependency property 
        /// indicates if the element is scalable.
        /// </summary>
        public static void SetIsScalable(DependencyObject d, bool value)
        {
            d.SetValue(IsScalableProperty, value);
        }

        #endregion

        #region EnableWalls

        public bool EnableWalls
        {
            get;
            set;
        }

        #endregion

        #endregion

        #region ILayoutPolicy Members

        public override void OnClosed()
        {
            this.timer.Dispose();
        }

        public override void OnInputReceived(IObject obj, InputEventArgs args)
        {
            base.OnInputReceived(obj, args);
            if (args is MultiTouchEventArgs)
            {
                GetMultiTouchInput(obj, args as MultiTouchEventArgs);
            }
        }

        public override void TranslateObject(IObject obj, Point p)
        {
            base.TranslateObject(obj, p);
            SetPosition(obj, p);
        }

        public override void RotateObject(IObject obj, double angle)
        {
            base.RotateObject(obj, angle);
            SetOrientation(obj, angle);
        }

        public override void ResizeObject(IObject obj, Size size)
        {
            base.ResizeObject(obj, size);
            SetSize(obj, size);
        }

        public override void ScaleObject(IObject obj, Size scale)
        {
            base.ScaleObject(obj, scale);
            SetScale(obj, scale);
        }

        public override void OnObjectRemoved(IObject obj)
        {
            base.OnObjectRemoved(obj);
            RemoveBody(obj);
        }

        public override void OnObjectInserted(IObject obj)
        {
            base.OnObjectInserted(obj);
            CreateBody(obj);
        }

        public override void OnObjectClosed(IObject obj)
        {
            base.OnObjectClosed(obj);
            RemoveBody(obj);
        }

        public override void JoinObjects(ObjectJoinCommandArgs args)
        {
            IObject obj1 = args.Obj1;
            IObject obj2 = args.Obj2;
            if (obj1 == null || !elementToBody.ContainsKey(obj1) || obj2 == null || !elementToBody.ContainsKey(obj2))
            {
                return;
            }
            Body body1 = elementToBody[obj1];
            Body body2 = elementToBody[obj2];

            // join two object

            Point position = obj1.Position;
            Vector2D contactPoint = new Vector2D((float)(position.X + obj1.Width), (float)(position.Y + obj1.Height));
            HingeJoint joint = new HingeJoint(body1, body2, contactPoint, new Lifespan());
            HingeJoint joint2 = new HingeJoint(body1, body2, new Vector2D((float)position.X, (float)(position.Y + obj1.MinHeight)), new Lifespan());
            AngleJoint join3 = new AngleJoint(body1, body2, new Lifespan());
            engine.AddJoint(joint);
            // engine.AddJoint(joint2);
            engine.AddJoint(join3);
            JoinInfo info = new JoinInfo();
            info.Obj1 = obj1;
            info.Obj2 = obj2;
            info.Joints.Add(joint);
            info.Joints.Add(join3);
            JoinInfoList.Add(info);
        }

        public override void UnjoinObjects(ObjectUnjoinCommandArgs args)
        {
            // should be implemented by the inherited class
            IObject obj1 = args.Obj1;
            IObject obj2 = args.Obj2;
            if (obj1 == null || !elementToBody.ContainsKey(obj1) || obj2 == null || !elementToBody.ContainsKey(obj2))
            {
                return;
            }
            JoinInfo target = null;
            foreach (JoinInfo info in JoinInfoList)
            {
                if (info.Obj1 == obj1 && info.Obj2 == obj2)
                {
                    target = info;
                }
            }
            if (target != null)
            {
                foreach (Joint j in target.Joints)
                {
                    j.Lifetime.IsExpired = true;
                }
                JoinInfoList.Remove(target);
            }
        }

        public override void ChangeLayoutState(IObject obj, ObjectLayoutState state)
        {
            base.ChangeLayoutState(obj, state);

            if (state == ObjectLayoutState.Maximized)
            {
                // Maximized Object
                // There can only be an maximized object at one time

                // firstly save the object info for restore
                _maximizedObjectInfo = new ObjectInfo();
                _maximizedObjectInfo.Obj = obj;
                _maximizedObjectInfo.Height = obj.Height;
                _maximizedObjectInfo.Width = obj.Width;
                _maximizedObjectInfo.ScaleX = obj.ScaleX;
                _maximizedObjectInfo.ScaleY = obj.ScaleY;
                _maximizedObjectInfo.Orientation = obj.Orientation;
                _maximizedObjectInfo.Position = obj.Position;
                _maximizedObjectInfo.RotateCenter = obj.RotateCenter;
                _maximizedObjectInfo.ScaleCenter = obj.ScaleCenter;

                // secondly maximize the object
                obj.Width = Target.Width;
                obj.Height = Target.Height;
                obj.Orientation = 0;
                obj.ScaleX = 1;
                obj.ScaleY = 1;
                obj.Position = new Point(0, 0);
                SetZTop(obj);
            }
            if (state == ObjectLayoutState.Normal)
            {
                // Restore Object
                if (_maximizedObjectInfo == null || _maximizedObjectInfo.Obj != obj)
                    return;
                obj.Width = _maximizedObjectInfo.Width;
                obj.Height = _maximizedObjectInfo.Height;
                obj.Orientation = _maximizedObjectInfo.Orientation;
                obj.ScaleX = _maximizedObjectInfo.ScaleX;
                obj.ScaleY = _maximizedObjectInfo.ScaleY;

                // reset the objectInfo
                _maximizedObjectInfo = null;
            }
        }

        #endregion
    }
}
